package com.gradecak.alfresco.mvc;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;

import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.Description.RequiredTransaction;
import org.springframework.extensions.webscripts.Description.TransactionCapability;
import javax.transaction.Status;
import javax.transaction.UserTransaction;

import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.WrappingWebScriptResponse;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;

import com.gradecak.alfresco.mvc.servlet.FrameworkDispatcherServlet;
import com.gradecak.alfresco.mvc.servlet.FrameworkServletConfig;
import com.gradecak.alfresco.mvc.servlet.FrameworkServletRequestProxy;

public class DispatcherWebScript extends AbstractWebScript implements
		InitializingBean, ServletContextAware, ApplicationContextAware {

	private static final Log logger = LogFactory
			.getLog(DispatcherWebScript.class);
	private FrameworkDispatcherServlet s;
	private ServletContext servletContext;
	private String contextConfigLocation;
	private ApplicationContext applicationContext;
	private ServiceRegistry serviceRegistry;

	public void execute(final WebScriptRequest req, final WebScriptResponse res) {
		final FrameworkServletRequestProxy proxy = new FrameworkServletRequestProxy(
				(WebScriptServletRequest) req);

		try {

			WebScriptServletResponse wsr = null;
			if (res instanceof WrappingWebScriptResponse) {

				wsr = ((WebScriptServletResponse) ((WrappingWebScriptResponse) res)
						.getNext());
			} else {
				wsr = (WebScriptServletResponse) res;
			}

			final HttpServletResponse sr = wsr.getHttpServletResponse();
			
			// which should be the primary @Transactional or Alfresco webscript?
			final Description description = s.getDescription(proxy);			
			//final Description description = this.getDescription();
			
			if (description == null || description.getRequiredTransaction() == RequiredTransaction.none) {
				s.service(proxy, sr);
			} else {

				RetryingTransactionCallback<Object> txnWork = new RetryingTransactionCallback<Object>() {
					public Object execute() throws Exception {
						try {
							s.service(proxy, sr);
						} catch (Exception e) {
							 if (logger.isDebugEnabled())
		                        {
		                            logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage());
		                            // Note: user transaction shouldn't be null, but just in case inside this exception handler
		                            UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
		                            if (userTrx != null)
		                            {
		                                logger.debug("Transaction status: " + userTrx.getStatus());
		                            }
		                        }
		                        
		                        UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
		                        if (userTrx != null)
		                        {
		                            if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK)
		                            {
		                                if (logger.isDebugEnabled())
		                                    logger.debug("Marking web script transaction for rollback");
		                                try
		                                {
		                                    userTrx.setRollbackOnly();
		                                }
		                                catch(Throwable re)
		                                {
		                                    if (logger.isDebugEnabled())
		                                        logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage());
		                                }
		                            }
		                        }
		                        
		                        // re-throw original exception for retry
		                        throw e;
						}

						return null;
					}
				};

				boolean readonly = description
						.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly;
				boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew;
				this.serviceRegistry.getTransactionService()
						.getRetryingTransactionHelper()
						.doInTransaction(txnWork, readonly, requiresNew);
			}

		} catch (Exception e) {
			String msg = "Could not process with the request for "
					+ proxy.getRequestURI();
			logger.error(msg, e);
			throw new FrameworkRuntimeException(msg, e);
		}
	}

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(contextConfigLocation,
				"contextConfigLocation must be initialized");
		Assert.notNull(servletContext, "servletContext must be initialized");

		s = new FrameworkDispatcherServlet(
				(WebApplicationContext) this.applicationContext);
		s.setContextConfigLocation(contextConfigLocation);
		s.init(new FrameworkServletConfig(servletContext));

		if (this.serviceRegistry == null) {
			this.serviceRegistry = (ServiceRegistry) applicationContext
					.getBean("ServiceRegistry");
		}
		logger.info("DispatcherWebScript initialized");
	}

	public void setContextConfigLocation(String contextConfigLocation) {
		this.contextConfigLocation = contextConfigLocation;
	}

	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}

	public void setServiceRegistry(ServiceRegistry serviceRegistry) {
		this.serviceRegistry = serviceRegistry;
	}
}
